import axios, {
  AxiosInstance,
  AxiosRequestConfig,
  AxiosPromise,
  AxiosResponse
} from 'axios'
import { ElMessage } from 'element-plus'
import { cleanSession, getToken } from '@/utils/auth'
import { LoginParm } from '@/api/user/UserModel'
import qs from 'qs'

//返回值类型 T代表的是泛型any
export interface Result<T = any> {
  code: number
  msg: string
  data: T
}

//返回的状态码
export enum StatusCode {
  NoAuth = 600, //无权限
  Success = 200 //返回成功
}
// 整个文件就是定义好的一个类
class request {
  // axios实例
  private instance: AxiosInstance
  // 构造函数，给instance进行初始化
  constructor (config: AxiosRequestConfig) {
    // 创建axios实例
    this.instance = axios.create(config)
    // 拦截器配置
    this.interceptors()
  }
  // 拦截器
  private interceptors () {
    // 请求拦截器，添加token
    this.instance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        let token = getToken()
        if (token) {
          // 换一种写法
          // if (config.headers) {
          //   config.headers.token = token
          // } else {
          //   config.headers = { token: token }
          // }
          config.headers = {
            // 为了防止覆盖原来定好的headers中的请求方式
            ...config.headers,
            token: token
          }
        }
        return config
      },
      (error: any) => {
        // 错误抛到业务代码
        error.data = {}
        error.data.msg = '服务器异常，请联系管理员!'
        return error
      }
    )
    // 响应拦截器
    this.instance.interceptors.response.use(
      (res: AxiosResponse) => {
        // console.log(res);
        if (res && res.data) {
          const data = res.data
          // console.log(data);
          if (data.code == StatusCode.NoAuth) {
            // 跳转登录token过期
            cleanSession()
            // 这里如果不跳转登录页面，在收到token过期的信息之后，session中的内容会被
            // 全部清空，导致tab栏失效，不在显示这也是tab栏一直静置久了就消失的原因
            window.location.href = '/login'
            console.log('进到这里了')
          } else if (
            data.code == StatusCode.Success ||
            res.config.responseType === 'arraybuffer'
          ) {
            // if (res.config.responseType === 'arraybuffer') {
            //   console.log('这里是arraybuffer');
            // }
            return res
          } else {
            ElMessage.error(data.msg || '服务器出错!')
            return res || null
          }
        }
      },
      error => {
        console.log('进入错误')
        if (error && error.response) {
          error.data = {}
          switch (error.response.status) {
            case 400:
              error.data.msg = '错误请求'
              ElMessage.error(error.data.msg)
              break
            case 401:
              error.data.msg = '未授权，请重新登录'
              ElMessage.error(error.data.msg)
              break
            case 403:
              error.data.msg = '拒绝访问'
              ElMessage.error(error.data.msg)
              break
            case 404:
              error.data.msg = '请求错误,未找到该资源'
              ElMessage.error(error.data.msg)
              break
            case 405:
              error.data.msg = '请求方法未允许'
              ElMessage.error(error.data.msg)
              break
            case 408:
              error.data.msg = '请求超时'
              ElMessage.error(error.data.msg)
              break
            case 500:
              error.data.msg = '服务器端出错'
              ElMessage.error(error.data.msg)
              break
            case 501:
              error.data.msg = '网络未实现'
              ElMessage.error(error.data.msg)
              break
            case 502:
              error.data.msg = '网络错误'
              ElMessage.error(error.data.msg)
              break
            case 503:
              error.data.msg = '服务不可用'
              ElMessage.error(error.data.msg)
              break
            case 504:
              error.data.msg = '网络超时'
              ElMessage.error(error.data.msg)
              break
            case 505:
              error.data.msg = 'http版本不支持该请求'
              ElMessage.error(error.data.msg)
              break
            default:
              error.data.msg = `连接错误${error.response.status}`
              ElMessage.error(error.data.msg)
          }
        } else {
          error.data.msg = '连接到服务器失败'
          ElMessage.error(error.data.msg)
        }
        return error
      }
    )
  }

  // 下面都是对这些请求方式的一个封装，这里给封装成promise，包括工具类中的confirm这个确定框
  // 也给封装成为了promise，最主要的目的其实还是为了方便对于这个方法结果的处理
  // 比如编辑和删除，就可以区分调用方法，但是用同一段代码来处理结果
  // 还有就是确认框，可以直接拿到结果，然后进行判定，不用把代码都写到自己的主页面中
  // 也就不需要在主页面进行判断

  //  {userId : 10}
  //http:localhost:8080/api/getUserByid?userId=10
  // 如果是加问号传递过去，就是至二级qs就可以，如果需要删除键名，需要单独处理
  get<T = any> (url: string, parms?: any): Promise<Result<T>> {
    return new Promise((resolve, reject) => {
      this.instance
        .get<T>(url, {
          params: parms,
          paramsSerializer: parms => {
            return qs.stringify(parms)
          }
        })
        .then(res => {
          resolve(res.data as any)
        })
        .catch(error => {
          reject(error)
        })
    })
  }

  // 参数的处理,就是把属性值拿出来，然后添加一个/，再把最后一位的/去掉就可以了
  // 也可以把/加在前面，提供另外一个思路,是可行的，也更为简单一些
  // 这里保存这个原有的方法，可以记录一下字符串截取的方式
  getParms (parms: any) {
    let _parms = ''
    if (Object.is(parms, undefined || null)) {
      _parms = ''
    } else {
      for (const key in parms) {
        if (parms.hasOwnProperty(key) && parms[key]) {
          _parms += `${parms[key]}/`
        }
      }
    }
    // 去掉参数最后一位的/
    if (_parms) {
      _parms = _parms.substr(0, _parms.length - 1)
    }
    return _parms
  }

  // {userId : 10,useName : 'test'}  ==>  10/test
  //http:localhost:8080/api/getUserByid/10/test
  //http:localhost:8080/api/getUserByid
  getRestApi<T = any> (url: string, parms?: any): Promise<Result<T>> {
    return new Promise((resolve, reject) => {
      this.instance
        // 这里的写法，有参数就拼接上，没有参数就只发送一个地址过去
        .get<T>(this.getParms(parms) ? `${url}/${this.getParms(parms)}` : url)
        .then(res => {
          // resolve和reject是一定要加上的，这两个关键字是用来修改promise的状态的
          // 这两个不能缺少，否则异步方法无法调用
          resolve(res.data as any)
        })
        .catch(error => {
          reject(error)
        })
    })
  }

  //post请求
  // 注意一下post请求的传参方式，这里没有对参数进行其他的操作和分割
  // 使用qs进行了参数的序列化
  post<T = any> (url: string, parms: any): Promise<Result<T>> {
    return new Promise((resolve, reject) => {
      this.instance
        .post(url, parms, {
          transformRequest: [
            params => {
              return JSON.stringify(params)
            }
          ],
          headers: {
            'Content-Type': 'application/json'
          }
        })
        .then(res => {
          resolve(res.data as any)
        })
        .catch(error => {
          reject(error)
        })
    })
  }

  //put请求
  put<T = any> (url: string, parms: any): Promise<Result<T>> {
    return new Promise((resolve, reject) => {
      this.instance
        .put(url, parms, {
          transformRequest: [
            params => {
              return JSON.stringify(params)
            }
          ],
          headers: {
            'Content-Type': 'application/json'
          }
        })
        .then(res => {
          resolve(res.data as any)
        })
        .catch(error => {
          reject(error)
        })
    })
  }

  //删除
  // {userId : 10}  ==>  10
  //http:localhost:8080/api/deleteById/10
  delete<T = any> (url: string, parms?: any): Promise<Result<T>> {
    return new Promise((resolve, reject) => {
      this.instance
        .delete<T>(
          this.getParms(parms) ? `${url}/${this.getParms(parms)}` : url
        )
        .then(res => {
          resolve(res.data as any)
        })
        .catch(error => {
          reject(error)
        })
    })
  }

  //获取验证码
  getImage (url: string) {
    return this.instance.post(url, null, {
      responseType: 'arraybuffer'
    })
  }

  /**
   * 登录接口需要formdate格式的数据，这里需要做一下处理
   * 具体的方法就是添加headers请求头，格式为'Content-Type': 'application/x-www-form-urlencoded'
   * 这样就可以自动把请求数据转换成formdate格式
   * 登录接口默认是一个泛型any,如果传入具体的类型，那就赋值为具体的类型
   * 这里的params也是先给一个any类型，后面可以传入自己想要的类型
   * params类型目前已知是固定的，那就可以定义它的类型，这里统一写在api/user/UserModels下处理
   */
  login<T = any> (url: string, params: LoginParm): Promise<Result<T>> {
    return new Promise((resolve, reject) => {
      this.instance
        .post<T>(url, params, {
          transformRequest: [
            params => {
              return qs.stringify(params)
            }
          ],
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
          }
        })
        .then(res => {
          resolve(res as any)
        })
        .catch(error => {
          reject(error)
        })
    })
  }
}

export default request
